#include <drivers.h>
#include <drivers/pin.h>
#include <stm32f10x.h>
#include <string.h>
#include <config.h>

#define PIN_NUM(port, no) (((((port) & 0xFu) << 4) | ((no) & 0xFu)))
#define PIN_PORT(pin) ((uint8_t)(((pin) >> 4) & 0xFu))
#define PIN_NO(pin) ((uint8_t)((pin) & 0xFu))

#define PIN_STPORT(pin) ((GPIO_TypeDef *)(GPIOA_BASE + (0x400u * PIN_PORT(pin))))

#define PIN_STPIN(pin) ((uint16_t)(1u << PIN_NO(pin)))

#if defined(GPIOZ)
#define __STM32_PORT_MAX 12u
#elif defined(GPIOK)
#define __STM32_PORT_MAX 11u
#elif defined(GPIOJ)
#define __STM32_PORT_MAX 10u
#elif defined(GPIOI)
#define __STM32_PORT_MAX 9u
#elif defined(GPIOH)
#define __STM32_PORT_MAX 8u
#elif defined(GPIOG)
#define __STM32_PORT_MAX 7u
#elif defined(GPIOF)
#define __STM32_PORT_MAX 6u
#elif defined(GPIOE)
#define __STM32_PORT_MAX 5u
#elif defined(GPIOD)
#define __STM32_PORT_MAX 4u
#elif defined(GPIOC)
#define __STM32_PORT_MAX 3u
#elif defined(GPIOB)
#define __STM32_PORT_MAX 2u
#elif defined(GPIOA)
#define __STM32_PORT_MAX 1u
#else
#define __STM32_PORT_MAX 0u
#error Unsupported STM32 GPIO peripheral.
#endif

#define PIN_STPORT_MAX __STM32_PORT_MAX

struct pin_irq_map
{
    uint16_t pin;
    IRQn_Type irqno;
    uint32_t exti_line;
};

const static struct pin_irq_map _pin_irq_map[] = {
	{GPIO_Pin_0, EXTI0_IRQn, EXTI_Line0},
	{GPIO_Pin_1, EXTI1_IRQn, EXTI_Line1},
	{GPIO_Pin_2, EXTI2_IRQn, EXTI_Line2},
	{GPIO_Pin_3, EXTI3_IRQn, EXTI_Line3},
	{GPIO_Pin_4, EXTI4_IRQn, EXTI_Line4},
	{GPIO_Pin_5, EXTI9_5_IRQn, EXTI_Line5},
	{GPIO_Pin_6, EXTI9_5_IRQn, EXTI_Line6},
	{GPIO_Pin_7, EXTI9_5_IRQn, EXTI_Line7},
	{GPIO_Pin_8, EXTI9_5_IRQn, EXTI_Line8},
	{GPIO_Pin_9, EXTI9_5_IRQn, EXTI_Line9},
	{GPIO_Pin_10, EXTI15_10_IRQn, EXTI_Line10},
	{GPIO_Pin_11, EXTI15_10_IRQn, EXTI_Line11},
	{GPIO_Pin_12, EXTI15_10_IRQn, EXTI_Line12},
	{GPIO_Pin_13, EXTI15_10_IRQn, EXTI_Line13},
	{GPIO_Pin_14, EXTI15_10_IRQn, EXTI_Line14},
	{GPIO_Pin_15, EXTI15_10_IRQn, EXTI_Line15},
};

static struct pin_irq_handler _pin_irq_handler[] = {
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
	{-1, 0, NULL, NULL},
};

static uint32_t pin_irq_enable_mask = 0;

static int8_t get_index_form_pin(const base_t pin)
{
	int8_t i = 0;
	for (; i < 32; i++) {
		if (BIT(i) == pin)
			return i;
	}

	return -1;
}

static base_t pin_get(const char *pin_name)
{
	base_t pin = 0;
	int hw_port_num, hw_pin_num = 0;
	int i, name_len;

	name_len = strlen(pin_name);

	if ((name_len < 3) || (name_len >= 5))
		return -1;

	if (pin_name[0] != 'P')
		return -1;

	if ((pin_name[1] >= 'A') && (pin_name[1] <= 'Z'))
		hw_port_num = (int)(pin_name[1] - 'A');
	else
		return -1;

	for (i = 2; i < name_len; i++) {
		hw_pin_num *= 10;
		hw_pin_num += pin_name[i] - '0';
	}

	pin = PIN_NUM(hw_port_num, hw_pin_num);

	return pin;
}

static err_t pin_mode(const base_t pin, const enum pin_mode mode)
{
	GPIO_InitTypeDef gpio_init_struct;

	if (PIN_PORT(pin) >= PIN_STPORT_MAX)
		return -1;

	gpio_init_struct.GPIO_Pin = PIN_STPIN(pin);
	gpio_init_struct.GPIO_Mode = GPIO_Mode_Out_PP;
	gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;

	if (mode == PIN_MODE_OUTPUT_OD)
		gpio_init_struct.GPIO_Mode = GPIO_Mode_Out_OD;
	else if (mode == PIN_MODE_AF_OD)
		gpio_init_struct.GPIO_Mode = GPIO_Mode_AF_OD;
	else if (mode == PIN_MODE_AF_PP)
		gpio_init_struct.GPIO_Mode = GPIO_Mode_AF_PP;
	else if (mode == PIN_MODE_INPUT)
		gpio_init_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	else if (mode == PIN_MODE_INPUT_PULL_DOWN)
		gpio_init_struct.GPIO_Mode = GPIO_Mode_IPD;
	else if (mode == PIN_MODE_INPUT_PULL_UP)
		gpio_init_struct.GPIO_Mode = GPIO_Mode_IPU;

	GPIO_Init(PIN_STPORT(pin), &gpio_init_struct);

	return 0;
}

static err_t pin_write(const base_t pin, const base_t value)
{
	GPIO_TypeDef *gpio_port;
	uint16_t gpio_pin;

	if (PIN_PORT(pin) >= PIN_STPORT_MAX)
		return -1;

	gpio_port = PIN_STPORT(pin);
	gpio_pin = PIN_STPIN(pin);

	GPIO_WriteBit(gpio_port, gpio_pin, (BitAction)value);

	return 0;
}

static base_t pin_read(const base_t pin)
{
	GPIO_TypeDef *gpio_port;
	uint16_t gpio_pin;

	if (PIN_PORT(pin) >= PIN_STPORT_MAX)
		return -1;

	gpio_port = PIN_STPORT(pin);
	gpio_pin = PIN_STPIN(pin);

	return GPIO_ReadInputDataBit(gpio_port, gpio_pin);
}

static err_t pin_attach_irq(const base_t pin,
				const enum pin_irq_mode mode,
				void (*handler)(void *args),
				void *args,
				uint8_t priority)
{
	int8_t index = get_index_form_pin(PIN_STPIN(pin));
	if (index < 0)
		return index;

	if (_pin_irq_handler[index].handler == handler &&
		_pin_irq_handler[index].args == args &&
		_pin_irq_handler[index].mode == mode &&
		_pin_irq_handler[index].pin == pin &&
		_pin_irq_handler[index].priority == priority)
		return 0;

	if (_pin_irq_handler[index].pin != -1)
		return -1;

	_pin_irq_handler[index].handler = handler;
	_pin_irq_handler[index].args = args;
	_pin_irq_handler[index].mode = mode;
	_pin_irq_handler[index].pin = pin;
	_pin_irq_handler[index].priority = priority;

	return 0;
}

static err_t pin_detach_irq(const base_t pin)
{
	int8_t index = -1;
	if (PIN_PORT(pin) >= PIN_STPORT_MAX)
		return -1;

	index = get_index_form_pin(PIN_STPIN(pin));
	if (index < 0)
		return -1;

	if (_pin_irq_handler[index].pin == -1)
		return 0;

	_pin_irq_handler[index].pin = -1;
	_pin_irq_handler[index].args = NULL;
	_pin_irq_handler[index].handler = NULL;
	_pin_irq_handler[index].mode = 0;

	return 0;
}

static err_t pin_enable_irq(const base_t pin, const uint8_t enable)
{
	int8_t index = -1;
	const struct pin_irq_map *irq_map;
	NVIC_InitTypeDef nvic_init_struct;
	EXTI_InitTypeDef exti_init_struct;

	if (PIN_PORT(pin) >= PIN_STPORT_MAX)
		return -1;

	index = get_index_form_pin(PIN_STPIN(pin));
	if (index < 0)
		return -1;

	irq_map = &_pin_irq_map[index];
	if (irq_map == NULL)
		return -1;

	if (enable == true) {
		nvic_init_struct.NVIC_IRQChannel = irq_map->irqno;
		nvic_init_struct.NVIC_IRQChannelCmd = ENABLE;

		nvic_init_struct.NVIC_IRQChannelPreemptionPriority =
			_pin_irq_handler[index].priority & 0x0F;
		nvic_init_struct.NVIC_IRQChannelSubPriority =
			(_pin_irq_handler[index].priority >> 4) & 0x0F;

		exti_init_struct.EXTI_Line = irq_map->exti_line;
		exti_init_struct.EXTI_LineCmd = ENABLE;
		exti_init_struct.EXTI_Mode = EXTI_Mode_Interrupt;

		switch (_pin_irq_handler[index].mode) {
		case PIN_IRQ_MODE_FALLING:
			exti_init_struct.EXTI_Trigger = EXTI_Trigger_Falling;
			break;
		case PIN_IRQ_MODE_RISING:
			exti_init_struct.EXTI_Trigger = EXTI_Trigger_Rising;
			break;
		case PIN_IRQ_MODE_RISING_FALLING:
			exti_init_struct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
			break;
		default:
			exti_init_struct.EXTI_Trigger = EXTI_Trigger_Falling;
			break;
		};

		pin_irq_enable_mask |= irq_map->pin;

		GPIO_EXTILineConfig(PIN_PORT(pin), index);
		EXTI_Init(&exti_init_struct);
		NVIC_Init(&nvic_init_struct);
	} else if (enable == false) {
		if ((pin_irq_enable_mask & irq_map->pin) == 0)
			return -1;

		pin_irq_enable_mask &= ~(irq_map->pin);

		if (irq_map->pin >= GPIO_Pin_5 && irq_map->pin <= GPIO_Pin_9) {
			if ((pin_irq_enable_mask & GPIO_Pin_5) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_6) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_7) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_8) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_9) != 0)
			return -1;
		}

		if (irq_map->pin >= GPIO_Pin_10 && irq_map->pin <= GPIO_Pin_15) {
			if ((pin_irq_enable_mask & GPIO_Pin_10) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_11) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_12) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_13) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_14) != 0 ||
				(pin_irq_enable_mask & GPIO_Pin_15) != 0)
				return -1;
		}

		nvic_init_struct.NVIC_IRQChannel = irq_map->irqno;
		nvic_init_struct.NVIC_IRQChannelCmd = DISABLE;

		exti_init_struct.EXTI_Line = irq_map->exti_line;
		exti_init_struct.EXTI_LineCmd = DISABLE;

		EXTI_Init(&exti_init_struct);
		NVIC_Init(&nvic_init_struct);
	} else {
		return -1;
	}

	return 0;
}

static struct pin_ops _pin_ops = {
	.pin_get = pin_get,
	.mode = pin_mode,
	.write = pin_write,
	.read = pin_read,
	.attach_irq = pin_attach_irq,
	.detach_irq = pin_detach_irq,
	.enable_irq = pin_enable_irq,
};

void EXTI0_IRQHandler(void)
{
	EXTI_ClearITPendingBit(EXTI_Line0);
	if ((pin_irq_enable_mask & GPIO_Pin_0) != 0)
		_pin_irq_handler[0].handler(_pin_irq_handler[0].args);
}

void EXTI1_IRQHandler(void)
{
	EXTI_ClearITPendingBit(EXTI_Line1);
	if ((pin_irq_enable_mask & GPIO_Pin_1) != 0)
		_pin_irq_handler[1].handler(_pin_irq_handler[1].args);
}

void EXTI2_IRQHandler(void)
{
	EXTI_ClearITPendingBit(EXTI_Line2);
	if ((pin_irq_enable_mask & GPIO_Pin_2) != 0)
		_pin_irq_handler[2].handler(_pin_irq_handler[2].args);
}

void EXTI3_IRQHandler(void)
{
	EXTI_ClearITPendingBit(EXTI_Line3);
	if ((pin_irq_enable_mask & GPIO_Pin_3) != 0)
		_pin_irq_handler[3].handler(_pin_irq_handler[3].args);
}

void EXTI4_IRQHandler(void)
{
	EXTI_ClearITPendingBit(EXTI_Line4);
	if ((pin_irq_enable_mask & GPIO_Pin_4) != 0)
		_pin_irq_handler[4].handler(_pin_irq_handler[4].args);
}

void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line5)) {
		EXTI_ClearITPendingBit(EXTI_Line5);
		if ((pin_irq_enable_mask & GPIO_Pin_5) != 0)
			_pin_irq_handler[5].handler(_pin_irq_handler[5].args);
	}

	if(EXTI_GetITStatus(EXTI_Line6)) {
		EXTI_ClearITPendingBit(EXTI_Line6);
		if ((pin_irq_enable_mask & GPIO_Pin_6) != 0)
			_pin_irq_handler[6].handler(_pin_irq_handler[6].args);
	}

	if(EXTI_GetITStatus(EXTI_Line7)) {
		EXTI_ClearITPendingBit(EXTI_Line7);
		if ((pin_irq_enable_mask & GPIO_Pin_7) != 0)
			_pin_irq_handler[7].handler(_pin_irq_handler[7].args);
	}

	if(EXTI_GetITStatus(EXTI_Line8)) {
		EXTI_ClearITPendingBit(EXTI_Line8);
		if ((pin_irq_enable_mask & GPIO_Pin_8) != 0)
			_pin_irq_handler[8].handler(_pin_irq_handler[8].args);
	}

	if(EXTI_GetITStatus(EXTI_Line9)) {
		EXTI_ClearITPendingBit(EXTI_Line9);
		if ((pin_irq_enable_mask & GPIO_Pin_9) != 0)
			_pin_irq_handler[9].handler(_pin_irq_handler[9].args);
	}
}

void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line10)) {
		EXTI_ClearITPendingBit(EXTI_Line10);
		if ((pin_irq_enable_mask & GPIO_Pin_10) != 0)
			_pin_irq_handler[10].handler(_pin_irq_handler[10].args);
	}

	if(EXTI_GetITStatus(EXTI_Line11)) {
		EXTI_ClearITPendingBit(EXTI_Line11);
		if ((pin_irq_enable_mask & GPIO_Pin_11) != 0)
			_pin_irq_handler[11].handler(_pin_irq_handler[11].args);
	}

	if(EXTI_GetITStatus(EXTI_Line12)) {
		EXTI_ClearITPendingBit(EXTI_Line12);
		if ((pin_irq_enable_mask & GPIO_Pin_12) != 0)
			_pin_irq_handler[12].handler(_pin_irq_handler[12].args);
	}

	if(EXTI_GetITStatus(EXTI_Line13)) {
		EXTI_ClearITPendingBit(EXTI_Line13);
		if ((pin_irq_enable_mask & GPIO_Pin_13) != 0)
			_pin_irq_handler[13].handler(_pin_irq_handler[13].args);
	}

	if(EXTI_GetITStatus(EXTI_Line14)) {
		EXTI_ClearITPendingBit(EXTI_Line14);
		if ((pin_irq_enable_mask & GPIO_Pin_14) != 0)
			_pin_irq_handler[14].handler(_pin_irq_handler[14].args);
	}

	if(EXTI_GetITStatus(EXTI_Line15)) {
		EXTI_ClearITPendingBit(EXTI_Line15);
		if ((pin_irq_enable_mask & GPIO_Pin_15) != 0)
			_pin_irq_handler[15].handler(_pin_irq_handler[15].args);
	}
}

void device_pin_init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

#ifdef GPIOA_ENABLE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
#endif

#ifdef GPIOB_ENABLE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
#endif

#ifdef GPIOC_ENABLE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
#endif

#ifdef GPIOD_ENABLE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
#endif

#ifdef GPIOE_ENABLE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
#endif

#ifdef GPIOF_ENABLE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF, ENABLE);
#endif

#ifdef GPIOG_ENABLE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);
#endif

	hw_pin_register(PIN_DEVICE_NAME, &_pin_ops, NULL);
}
INIT_DEVICE_EXPORT(device_pin_init);

